package online.heycm.platform.canary.service;

import feign.Client;
import feign.Request;
import feign.Response;
import lombok.extern.slf4j.Slf4j;
import online.heycm.platform.canary.common.CanaryConstant;
import online.heycm.platform.canary.common.CanaryHolder;
import online.heycm.platform.canary.common.CanaryRule;
import online.heycm.platform.canary.common.CanaryRuleCache;
import online.heycm.platform.canary.common.CanaryStrategy;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 自定义OpenFeign灰度客户端实现
 *
 * <p>
 * 参考：{@link org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient}
 * OpenFeign 对 Spring Cloud LoadBalancer 的默认实现
 * </p>
 *
 * @author hey
 * @version 1.0
 * @date 2024/1/4 9:49
 */
@Slf4j
public class CanaryFeignClient implements Client {

    private final Client delegate;

    private final BlockingLoadBalancerClient loadBalancerClient;

    private final DiscoveryClient discoveryClient;

    private final AtomicInteger position;

    public CanaryFeignClient(Client delegate, BlockingLoadBalancerClient loadBalancerClient, DiscoveryClient discoveryClient) {
        this.delegate = delegate;
        this.loadBalancerClient = loadBalancerClient;
        this.discoveryClient = discoveryClient;
        this.position = new AtomicInteger((new Random()).nextInt(1000));
        log.info("CanaryFeignClient init...");
    }


    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        final URI originalUri = URI.create(request.url());
        String serviceId = originalUri.getHost();
        Assert.state(serviceId != null,
                "Request URI does not contain a valid hostname: " + originalUri);

        // 自定义获取实例规则
        ServiceInstance instance = getServiceInstance(request, serviceId, originalUri.getPath());

        if (instance == null) {
            String message = "Load balancer does not contain an instance for the service " + serviceId;
            log.warn(message);
            return Response.builder().request(request)
                    .status(HttpStatus.SERVICE_UNAVAILABLE.value())
                    .body(message, StandardCharsets.UTF_8).build();
        }
        String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
                .toString();
        Request newRequest = buildRequest(request, reconstructedUrl);
        return delegate.execute(newRequest, options);
    }

    public Response originalExecute(Request request, Request.Options options) throws IOException {
        final URI originalUri = URI.create(request.url());
        String serviceId = originalUri.getHost();
        Assert.state(serviceId != null,
                "Request URI does not contain a valid hostname: " + originalUri);
        ServiceInstance instance = loadBalancerClient.choose(serviceId);
        if (instance == null) {
            String message = "Load balancer does not contain an instance for the service " + serviceId;
            log.warn(message);
            return Response.builder().request(request)
                    .status(HttpStatus.SERVICE_UNAVAILABLE.value())
                    .body(message, StandardCharsets.UTF_8).build();
        }
        String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
                .toString();
        Request newRequest = buildRequest(request, reconstructedUrl);
        return delegate.execute(newRequest, options);
    }

    protected Request buildRequest(Request request, String reconstructedUrl) {
        return Request.create(request.httpMethod(), reconstructedUrl, request.headers(),
                request.body(), request.charset(), request.requestTemplate());
    }

    // Visible for Sleuth instrumentation
    public Client getDelegate() {
        return delegate;
    }

    /**
     * 轮询取实例
     */
    public ServiceInstance getRoundRobinServiceInstance(List<ServiceInstance> instances) {
        if (CollectionUtils.isEmpty(instances)) {
            return null;
        }
        if (instances.size() == 1) {
            return instances.get(0);
        }
        int pos = Math.abs(this.position.incrementAndGet());
        return instances.get(pos % instances.size());
    }

    /**
     * 获取正常实例，无常规实例时，返回轮询所有实例
     *
     * @param instances 常规实例
     * @param path      请求地址（目的是为了打印日志）
     * @return ServiceInstance
     */
    private ServiceInstance getNormalServiceInstance(List<ServiceInstance> instances, String path) {
        List<ServiceInstance> normalInstances = CanaryHolder.getAllNormalInstances(instances);
        if (CollectionUtils.isEmpty(normalInstances)) {
            log.warn("请求 {} 无正式服务!!!", path);
            return getRoundRobinServiceInstance(instances);
        }
        return getRoundRobinServiceInstance(normalInstances);
    }

    /**
     * 自定义获取实例规则，灰度实现
     *
     * @param request   Feign 当前请求
     * @param serviceId 服务ID
     * @param path      请求地址
     * @return ServiceInstance
     */
    private ServiceInstance getServiceInstance(Request request, String serviceId, String path) {
        // 取serviceId所有实例
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
        if (CollectionUtils.isEmpty(instances)) {
            return null;
        }

        // 没有灰度配置时，优先转发到常规实例
        if (!CanaryRuleCache.enable()) {
            return getNormalServiceInstance(instances, path);
        }

        // 不匹配灰度配置时，优先转发到常规实例
        List<CanaryRule> rules = CanaryHolder.match(serviceId, path);
        if (CollectionUtils.isEmpty(rules)) {
            return getNormalServiceInstance(instances, path);
        }

        Map<String, Collection<String>> headers = request.headers();
        CanaryRule rule = CanaryHolder.highestPriority(rules);
        CanaryStrategy strategy = CanaryStrategy.ofNullable(rule.getCanaryStrategy());

        // 特定请求头灰度
        if (strategy == CanaryStrategy.HEAD) {
            return headGrayStrategy(instances, rule, headers, path);
        }

        // 流量灰度
        if (strategy == CanaryStrategy.FLOW) {
            return flowGrayStrategy(instances, rule, headers, path);
        }

        return getNormalServiceInstance(instances, path);
    }

    /**
     * 特定请求头灰度转发
     *
     * @param instances 所有实例
     * @param rule      灰度规则
     * @param headers   请求头
     * @param path      请求路径
     * @return ServiceInstance
     */
    private ServiceInstance headGrayStrategy(List<ServiceInstance> instances, CanaryRule rule,
                                             Map<String, Collection<String>> headers, String path) {
        // 取请求头参数值 X-CANARY-HEAD，取不到请求头时，优先转发正常服务
        if (CollectionUtils.isEmpty(headers)) {
            return getNormalServiceInstance(instances, path);
        }
        Collection<String> canaryHead = headers.get(CanaryStrategy.HEAD.getHeader());
        if (CollectionUtils.isEmpty(canaryHead)) {
            return getNormalServiceInstance(instances, path);
        }
        String canaryValue = new ArrayList<>(canaryHead).get(0);

        // 请求头的值与灰度配置值对比，不匹配时，优先转发正常服务
        if (StringUtils.isEmpty(canaryValue) || !rule.getCanaryValue().contains(canaryValue)) {
            log.info("请求 {}:{} {} 不匹配灰度配置，不做灰度分发", CanaryStrategy.HEAD.getHeader(), canaryValue, path);
            return getNormalServiceInstance(instances, path);
        }

        // 过滤匹配的灰度实例
        List<ServiceInstance> grayInstances = CanaryHolder.getAllCanaryInstances(instances).stream().filter(inst -> {
            Map<String, String> metadata = inst.getMetadata();
            if (CollectionUtils.isEmpty(metadata)) {
                return false;
            }
            String metadataKey = metadata.get(CanaryStrategy.HEAD.getMetadata());
            return metadataKey != null && metadataKey.contains(canaryValue);
        }).collect(Collectors.toList());

        // 没有灰度实例时，优先转发正常服务
        if (CollectionUtils.isEmpty(grayInstances)) {
            log.info("请求 {}:{} {} 无匹配灰度服务，不做灰度分发", CanaryStrategy.HEAD.getHeader(), canaryValue, path);
            return getNormalServiceInstance(instances, path);
        }

        return getRoundRobinServiceInstance(grayInstances);
    }

    /**
     * 流量灰度转发，网关灰度做流量标记，下游服务的流量灰度只需根据请求头判断是否转发灰度即可，不需要做流量标记
     *
     * @param instances 所有实例
     * @param rule      灰度规则
     * @param headers   请求头
     * @param path      请求路径
     * @return ServiceInstance
     */
    private ServiceInstance flowGrayStrategy(List<ServiceInstance> instances, CanaryRule rule,
                                             Map<String, Collection<String>> headers, String path) {

        // 取请求头参数值 X-CANARY-FLOW，取不到请求头时，优先转发正常服务
        if (CollectionUtils.isEmpty(headers)) {
            return getNormalServiceInstance(instances, path);
        }
        Collection<String> canaryHead = headers.get(CanaryStrategy.FLOW.getHeader());
        if (CollectionUtils.isEmpty(canaryHead)) {
            return getNormalServiceInstance(instances, path);
        }
        String canaryValue = new ArrayList<>(canaryHead).get(0);

        // 判断是否已被灰度流量标记，若是，走灰度，否则走常规服务
        if (!CanaryConstant.CANARY_FLOW_VALUE.equals(canaryValue)) {
            return getNormalServiceInstance(instances, path);
        }

        // 过滤匹配的灰度实例
        List<ServiceInstance> grayInstances = CanaryHolder.getAllCanaryInstances(instances).stream().filter(inst -> {
            Map<String, String> metadata = inst.getMetadata();
            if (CollectionUtils.isEmpty(metadata)) {
                return false;
            }
            String metadataKey = metadata.get(CanaryStrategy.FLOW.getMetadata());
            return CanaryConstant.METADATA_FLOW_VALUE.equals(metadataKey);
        }).collect(Collectors.toList());

        // 没有灰度实例时，转发到普通实例
        if (CollectionUtils.isEmpty(grayInstances)) {
            log.info("请求 {}:{} {} 无匹配灰度服务，不做灰度分发", CanaryStrategy.FLOW.getHeader(), CanaryConstant.CANARY_FLOW_VALUE, path);
            return getNormalServiceInstance(instances, path);
        }

        return getRoundRobinServiceInstance(grayInstances);
    }
}
